%
% Copyright (c) 2024 Kangwei Xia
% Released under the LaTeX Project Public License v1.3c License.
% Repository: https://gitee.com/xkwxdyy/exam-zh
%

\NeedsTeXFormat{LaTeX2e}

\RequirePackage{expl3}

\ProvidesExplPackage {exam-zh-math} {2025-11-12} {v0.2.6}
  {exam-zh math module}

\RequirePackage { tabularray }
\RequirePackage { varwidth }
\RequirePackage { graphicx }
\RequirePackage { filehook }
\file_if_exist:nT { wrapstuff.sty }
  {
    \RequirePackage { wrapstuff }
    \AtEndOfPackageFile* { exam-zh-choices }
      {
        \AddToHook { env / choices / before }
          { \wrapstuffclear }
      }
  }


\keys_define:nn { exam-zh }
  { calculations .meta:nn = { exam-zh / calculations } {#1} }

%% calculations 环境 %%


% 图片相对于 label 的方位
\str_new:N \l__examzh_calculations_figure_position_str
% coffin type 的对齐方式
\str_new:N \l__examzh_calculations_coffin_align_str


\keys_define:nn { exam-zh / calculations }
  {
    % calculations 环境的 label 从几开始（但是其实是 index +  seq 函数的 ##1 才是最终的 index）
    index .int_set:N = \l__examzh_calculations_label_index_shift_int,
    % 每行多少个（等价于有多少列）
    columns .int_set:N = \l__examzh_calculations_column_int,
    % 图片相对于文字的位置（上下左右）
    fig-pos .choices:nn =
      { top, above, bottom, below, left, right, left-top }
      { \str_set:Nx \l__examzh_calculations_figure_position_str { \l_keys_choice_tl } },
    % 环境上方的额外距离
    top-sep .skip_set:N = \l__examzh_calculations_top_sep_skip,
    % 环境下方的额外距离
    bottom-sep .skip_set:N = \l__examzh_calculations_bottom_sep_skip,
    % 两列之间的水平间距
    hsep .skip_set:N = \l__examzh_calculations_hsep_skip,
    % 两行之间的垂直间距
    vsep .skip_set:N = \l__examzh_calculations_vsep_skip,
    % 整体的偏移量
    xshift .dim_set:N = \l__examzh_calculations_xshift_dim,
    hshift .dim_set:N = \l__examzh_calculations_xshift_dim,
    yshift .dim_set:N = \l__examzh_calculations_yshift_dim,
    vshift .dim_set:N = \l__examzh_calculations_yshift_dim,
    % label 的偏移量
    label-xshift .dim_set:N = \l__examzh_calculations_label_xshift_dim,
    label-hshift .dim_set:N = \l__examzh_calculations_label_xshift_dim,
    label-yshift .dim_set:N = \l__examzh_calculations_label_yshift_dim,
    label-vshift .dim_set:N = \l__examzh_calculations_label_yshift_dim,
    % 对齐方式
    align .choices:nn = 
      { t, m, b }
      { \str_set:Nx \l__examzh_calculations_coffin_align_str { \l_keys_choice_tl } }
  }
\keys_set:nn { exam-zh / calculations }
  {
    index        = 0,
    columns      = 2,
    fig-pos      = left-top,
    xshift       = 2em,
    hsep         = 2em,
    vsep         = 0pt,
    label-xshift = 0pt,
    align        = t,
    top-sep      = 1ex plus .5ex minus .5ex,
    bottom-sep   = 0pt,
  }

% item 的 index 指标（即第几个 item）
\int_new:N \l__examzh_calculations_item_index_int
% 将拼接后的 coffin 存到 seq 里
\seq_new:N \l__examzh_calculations_store_seq
% 上面的 seq 的 item 数
\int_new:N \l__examzh_calculations_store_seq_item_int
% 最终放在 tblr 里的内容
\tl_new:N \l__examzh_calculations_tblr_content_tl



% #1: 设置 calculations
% #2: 设置里面的 tblr
\NewDocumentEnvironment { calculations } { O{ } +O{ } }
  {
    \group_begin:
      \RenewDocumentCommand \item { O{ } }
        { \__examzh_calculations_item:n {##1} }
      \int_set:Nn \l__examzh_calculations_item_index_int {0}
      \seq_clear:N \l__examzh_calculations_store_seq
      \tl_clear:N \l__examzh_calculations_tblr_content_tl
      \keys_set:nn { exam-zh / calculations } {#1}
  }
  {
      % 结束收集
        \unskip
      \end{varwidth}
      % \end{minipage}
      \hcoffin_set_end:
      % 拼接 label 和 figure
      \__examzh_calculations_coffin_join:
      % 输出
      \__examzh_calculations_coffin_typeset:n {#2}
      % \par \int_use:N \l__examzh_calculations_item_index_int
    \group_end:
  }
% 拼接 label 和 figure
\cs_new:Npn \__examzh_calculations_coffin_join:
  {
    \int_step_inline:nn { \l__examzh_calculations_item_index_int }
      {
        \__examzh_calculations_coffin_join_position_set:n {##1}
        \seq_gput_right:Nn \l__examzh_calculations_store_seq
          {
            \__examzh_calculations_coffin_align_set:n {##1}
            \kern\l__examzh_calculations_hsep_skip
          }
      }
  }
\cs_new:Npn \__examzh_calculations_coffin_align_set:n #1
  {
    \str_case:Vn \l__examzh_calculations_coffin_align_str
      {
        {t} { \__examzh_calculations_coffin_align_set_t:n {#1} }
        {m} { \__examzh_calculations_coffin_align_set_m:n {#1} }
        {b} { \__examzh_calculations_coffin_align_set_b:n {#1} }
      }
  }
\cs_new:Npn \__examzh_calculations_coffin_align_set_t:n #1
  {
    \coffin_typeset:cnnnn
      { l__examzh_calculations_figure_ \int_to_roman:n { #1 } _coffin }
      { l } { t }  % align = t
      { \l__examzh_calculations_xshift_dim } { \l__examzh_calculations_yshift_dim + 1em }
  }
\cs_new:Npn \__examzh_calculations_coffin_align_set_m:n #1
  {
    \coffin_typeset:cnnnn
      { l__examzh_calculations_figure_ \int_to_roman:n { #1 } _coffin }
      { l } { vc }  % align = m
      { \l__examzh_calculations_xshift_dim } { \l__examzh_calculations_yshift_dim }
  }
\cs_new:Npn \__examzh_calculations_coffin_align_set_b:n #1
  {
    \coffin_typeset:cnnnn
      { l__examzh_calculations_figure_ \int_to_roman:n { #1 } _coffin }
      { l } { b }   % align = b
      { \l__examzh_calculations_xshift_dim } { \l__examzh_calculations_yshift_dim }
  }
\cs_new:Npn \__examzh_calculations_coffin_join_position_set:n #1
  {
    \use:c { __examzh_calculations_coffin_join_position_set_ \l__examzh_calculations_figure_position_str :n } {#1}
  }
\cs_new:cpn { __examzh_calculations_coffin_join_position_set_left-top :n } #1
  {
    \coffin_join:cnncnnnn
      { l__examzh_calculations_figure_ \int_to_roman:n {#1} _ coffin }
      { l } { t }
      { l__examzh_calculations_label_ \int_to_roman:n {#1} _ coffin }
      { r } { b }
      { \l__examzh_calculations_label_xshift_dim }
      { \l__examzh_calculations_label_yshift_dim -10.6pt}
  }
\cs_new:Npn \__examzh_calculations_coffin_join_position_set_top:n #1
  {
    \coffin_join:cnncnnnn
      { l__examzh_calculations_figure_ \int_to_roman:n {#1} _ coffin }
      { hc } { b }
      { l__examzh_calculations_label_ \int_to_roman:n {#1} _ coffin }
      { hc } { t }
      { \l__examzh_calculations_label_xshift_dim }
      { \l__examzh_calculations_label_yshift_dim - 6pt }
  }
\cs_set_eq:NN 
  \__examzh_calculations_coffin_join_position_set_above:n 
  \__examzh_calculations_coffin_join_position_set_top:n
\cs_new:Npn \__examzh_calculations_coffin_join_position_set_bottom:n #1
  {
    \coffin_join:cnncnnnn
      { l__examzh_calculations_figure_ \int_to_roman:n {#1} _ coffin }
      { hc } { t }
      { l__examzh_calculations_label_ \int_to_roman:n {#1} _ coffin }
      { hc } { b }
      { \l__examzh_calculations_label_xshift_dim }
      { \l__examzh_calculations_label_yshift_dim + 6pt }
  }
\cs_set_eq:NN 
  \__examzh_calculations_coffin_join_position_set_below:n 
  \__examzh_calculations_coffin_join_position_set_bottom:n
\cs_new:Npn \__examzh_calculations_coffin_join_position_set_left:n #1
  {
    \coffin_join:cnncnnnn
      { l__examzh_calculations_figure_ \int_to_roman:n {#1} _ coffin }
      { l } { vc }
      { l__examzh_calculations_label_ \int_to_roman:n {#1} _ coffin }
      { r } { vc }
      { \l__examzh_calculations_label_xshift_dim - 6pt }
      { \l__examzh_calculations_label_yshift_dim }
  }
\cs_new:Npn \__examzh_calculations_coffin_join_position_set_right:n #1
  {
    \coffin_join:cnncnnnn
      { l__examzh_calculations_figure_ \int_to_roman:n {#1} _ coffin }
      { r } { vc }
      { l__examzh_calculations_label_ \int_to_roman:n {#1} _ coffin }
      { l } { vc }
      { \l__examzh_calculations_label_xshift_dim + 2pt }
      { \l__examzh_calculations_label_yshift_dim }
  }
\cs_new:Npn \__examzh_calculations_coffin_typeset:n #1
  {
    \__examzh_calculations_coffin_typeset_count:
    \seq_map_indexed_inline:Nn \l__examzh_calculations_store_seq
      % ##1: index
      % ##2: content
      {
        \int_compare:nNnTF { \int_mod:nn {##1} { \l__examzh_calculations_column_int } } = {0}
          {
            \tl_gput_right:Nn \l__examzh_calculations_tblr_content_tl 
              { ##2 \\[\l__examzh_calculations_vsep_skip] }
          }
          {
            \tl_gput_right:Nn \l__examzh_calculations_tblr_content_tl 
              { ##2 & }
          }
      }
    % 如果 seq 的 item 比 column 多且不整除 column 的话，要补 &
    \int_compare:nNnT { \l__examzh_calculations_store_seq_item_int } > { \l__examzh_calculations_column_int }
      {
        \int_compare:nNnF { \l__examzh_calculations_item_num_mod_column_left_int } = { 0 }
          {
            \tl_gput_right:Nx \l__examzh_calculations_tblr_content_tl
              {
                \prg_replicate:nn { \l__examzh_calculations_item_num_mod_column_left_int -1 } {&}
              }
            % \int_use:N \l__examzh_calculations_item_num_mod_column_left_int
          }
      }
    \par 
    \vspace*{ \l__examzh_calculations_top_sep_skip }
    \noindent 
    % \centering
    % \SetTblrInner
    %   {
    %     rowsep = 4pt,
    %     % colsep = 0pt
    %   }
    \begin{tblr}
      [ expand = \l__examzh_calculations_tblr_content_tl ]
      {
        width = \linewidth,
        columns = 
          {
            l,
            leftsep = 0em,
            rightsep = 0pt,
          },
        rows = 
          {
            m,
            ht=\baselineskip,
            abovesep = 0pt,
            belowsep = 0pt,
          },
        stretch=0,
        #1
      }
      \l__examzh_calculations_tblr_content_tl
    \end{tblr}
    \vspace*{ \l__examzh_calculations_bottom_sep_skip }
    \par
  }
\int_new:N \l__examzh_calculations_item_num_mod_column_left_int
\cs_new:Npn \__examzh_calculations_coffin_typeset_count:
  {
    % 计算 seq 有多少项
    \int_set:Nn \l__examzh_calculations_store_seq_item_int
      { \seq_count:N \l__examzh_calculations_store_seq }
    % seq 项数小于 column 的话，column 设置为 seq 项数
    \int_compare:nNnTF { \l__examzh_calculations_store_seq_item_int } < { \l__examzh_calculations_column_int }
      { \int_set_eq:NN \l__examzh_calculations_column_int \l__examzh_calculations_store_seq_item_int }
      {
        % 计算 \l__examzh_calculations_store_seq_item_int mod \l__examzh_calculations_column_int 的余数，用于补 &
        \int_set:Nn \l__examzh_calculations_item_num_mod_column_left_int { \int_mod:nn { \l__examzh_calculations_store_seq_item_int } { \l__examzh_calculations_column_int } }
      }
  }

\cs_new:Npn \__examzh_calculations_item:n #1
  {
    % 增加指标（g 是关键）
    \int_gincr:N \l__examzh_calculations_item_index_int
    % 新建 coffin
    \__examzh_calculations_item_new_coffin:
    % 储存 label（一直出不来的原因是因为没改成 gset）
    \hcoffin_gset:cn 
      { l__examzh_calculations_label_ \int_to_roman:n { \l__examzh_calculations_item_index_int } _coffin } 
      {
        % 加上 index 键值设置的偏移量才是最终的 index
        % （\int_eval:n { \l__examzh_calculations_item_index_int + \l__examzh_calculations_label_index_shift_int - 1} ）
        % 如果 \item 的[] 中没内容，则需要把 label 升高
        % \tl_if_blank:nTF {#1} 
        %   {
        %     \box_move_up:nn  { 2.7pt } { \hbox{\__examzh_calculations_the_label:} }
        %     \space
        %     #1
        %   } 
        %   {
        %     \__examzh_calculations_the_label: #1
        %   }
        % \IfBooleanTF{#1}{#1 yes}{#1 no}
        % \IfNoValueTF{#1}
        %   {
        %     \dim_set:Nn \l__examzh_calculations_label_yshift_dim { 4pt }
        %   }{#1 yes}
      }
    % 储存 figure
    \int_compare:nNnF { \l__examzh_calculations_item_index_int } = {1}
      {
          \unskip
          \end{varwidth}
          % \end{minipage}
        % 结束上一个 item 的收集
        \hcoffin_set_end:
      }
    % 收集
    \hcoffin_set:cw { l__examzh_calculations_figure_ \int_to_roman:n { \l__examzh_calculations_item_index_int } _coffin }
      % \begin{varwidth}{\hsize}
    % 根据 columns 个数设置宽度
    \__examzh_calculations_item_varwidth_width_set:
      \begin{varwidth}{\l__examzh_calculations_item_varwidth_width_dim}
      % \begin{minipage}{8cm}
        \ignorespaces
        \makebox[0em]{\rule{0pt}{\baselineskip}}
        \__examzh_calculations_the_label:
  }
\dim_new:N \l__examzh_calculations_item_varwidth_width_dim
\cs_new:Npn \__examzh_calculations_item_varwidth_width_set:
  {
    \int_case:nnF { \l__examzh_calculations_column_int }
      {
        {1} { \dim_set:Nn \l__examzh_calculations_item_varwidth_width_dim {0.8 \textwidth} }
        {2} { \dim_set:Nn \l__examzh_calculations_item_varwidth_width_dim {0.45 \textwidth} }
        {3} { \dim_set:Nn \l__examzh_calculations_item_varwidth_width_dim {0.3 \textwidth} }
        {4} { \dim_set:Nn \l__examzh_calculations_item_varwidth_width_dim {0.2 \textwidth} }
      }
      { \dim_set:Nn \l__examzh_calculations_item_varwidth_width_dim { \hsize } }
  }

% 新建 coffin
\cs_new:Npn \__examzh_calculations_item_new_coffin:
  {
    % 放图片的 coffin
    \coffin_if_exist:cF { l__examzh_calculations_figure_ \int_to_roman:n { \l__examzh_calculations_item_index_int } _coffin }
      { \coffin_new:c { l__examzh_calculations_figure_ \int_to_roman:n { \l__examzh_calculations_item_index_int } _coffin } }
    % 放 label 的 coffin
    \coffin_if_exist:cF { l__examzh_calculations_label_ \int_to_roman:n { \l__examzh_calculations_item_index_int } _coffin }
      { \coffin_new:c { l__examzh_calculations_label_ \int_to_roman:n { \l__examzh_calculations_item_index_int } _coffin } }
  }

\tl_new:N \l__examzh_calculations_counters_commands_set_tl

\keys_define:nn { exam-zh / calculations }
  {
    label .tl_set:N = \l__examzh_calculations_label_tl
  }
\keys_set:nn { exam-zh / calculations }
  {
    label = \arabic*
  }
  
\cs_new:Npn \__examzh_calculations_the_label:
  {
    \group_begin:
      % 定义计数器相关的命令函数
      \l__examzh_calculations_counters_commands_set_tl
      % 输出处理后的 label
      \l__examzh_calculations_label_tl
    \group_end:
  }

\NewDocumentCommand \AddCalculationsCounter { m m }
  {
    % 生成用户层命令
    \tl_put_right:Nn \l__examzh_calculations_counters_commands_set_tl
      { \__examzh_process_counter:NNn #1 #2 { calculations } }
    % 把核心函数存起来
    \cs_set_eq:cN { __examzh_calculations_save_ \cs_to_str:N #1 : } #2
    \cs_set_eq:cN { __examzh_calculations_save_ \cs_to_str:N #2 : } #2
  }

\AddCalculationsCounter \arabic \@arabic
\AddCalculationsCounter \alph   \@alph
\AddCalculationsCounter \Alph   \@Alph
\AddCalculationsCounter \roman  \@roman
\AddCalculationsCounter \Roman  \@Roman

\cs_new:Npn \__examzh_calculations_process_counter_aux:Nn #1#2
  {
    \tl_if_eq:nnTF {#2} { * }
      {
        % \Alph*
        \use:c { __examzh_calculations_save_ \cs_to_str:N #1 : }
          { \int_eval:n { \l__examzh_calculations_item_index_int + \l__examzh_calculations_label_index_shift_int } }
      }
      {
        % \Alph{...}
        \use:c { __examzh_calculations_save_ \cs_to_str:N #1 : }
          {#2}
      }
  }